package motiveai.tests;

import com.jme3.renderer.RenderManager;
import motiveai.components.MotiveAI;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.VideoRecorderAppState;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapFont.VAlign;
import com.jme3.font.BitmapText;
import com.jme3.font.Rectangle;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.BillboardControl;
import com.jme3.scene.shape.Box;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import java.util.Iterator;
import motiveai.factors.EnvironmentDefinition;
import motiveai.factors.FactorBoolean;
import motiveai.factors.FactorInteger;
import motiveai.learn.LearningType;

/**
 * test
 * @author oxplay
 */
public class Test4_Advanced extends SimpleApplication {

    private MotiveAI ai;
    private EnvironmentDefinition env;
    private TerrainQuad terrain;
    private Material mat_terrain;
    private BitmapText globalText;

    public static void main(String[] args) {
        Test4_Advanced app = new Test4_Advanced();
        app.start();
    }

    private void addTerrain() {
        flyCam.setMoveSpeed(50);
        flyCam.setRotationSpeed(2f);
        /** 1. Create terrain material and load four textures into it. */
        mat_terrain = new Material(assetManager,
                "Common/MatDefs/Terrain/Terrain.j3md");

        /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
        mat_terrain.setTexture("Alpha", assetManager.loadTexture(
                "Textures/Terrain/splat/alphamap.png"));

        /** 1.2) Add GRASS texture into the red layer (Tex1). */
        Texture grass = assetManager.loadTexture(
                "Textures/Terrain/splat/grass.jpg");
        grass.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex1", grass);
        mat_terrain.setFloat("Tex1Scale", 64f);

        /** 1.3) Add DIRT texture into the green layer (Tex2) */
        Texture dirt = assetManager.loadTexture(
                "Textures/Terrain/splat/dirt.jpg");
        dirt.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex2", dirt);
        mat_terrain.setFloat("Tex2Scale", 32f);

        /** 1.4) Add ROAD texture into the blue layer (Tex3) */
        Texture rock = assetManager.loadTexture(
                "Textures/Terrain/splat/road.jpg");
        rock.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex3", rock);
        mat_terrain.setFloat("Tex3Scale", 128f);

        /** 2. Create the height map */
        AbstractHeightMap heightmap = null;
        Texture heightMapImage = assetManager.loadTexture(
                "Textures/Terrain/splat/mountains512.png");
        heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();

        /** 3. We have prepared material and heightmap. 
         * Now we create the actual terrain:
         * 3.1) Create a TerrainQuad and name it "my terrain".
         * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65.
         * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513.
         * 3.4) As LOD step scale we supply Vector3f(1,1,1).
         * 3.5) We supply the prepared heightmap itself.
         */
        int patchSize = 65;
        terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap());

        /** 4. We give the terrain its material, position & scale it, and attach it. */
        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, 0, 0);
        terrain.setLocalScale(2f, 0.1f, 2f);
        rootNode.attachChild(terrain);

        /** 5. The LOD (level of detail) depends on were the camera is: */
        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
        terrain.addControl(control);
    }

    private Node addUser(ColorRGBA color, Vector2f position) {
        Node userNode = new Node("Node" + color);
        userNode.setLocalTranslation(position.x, 1, position.y);
        Box box1 = new Box(1, 1, 1);
        Geometry geom = new Geometry("Box" + color, box1);
        geom.setLocalTranslation(0, 0, 0);
        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", color);
        geom.setMaterial(mat1);
        MoveControl moveControl = new MoveControl();
        userNode.addControl(moveControl);
        userNode.attachChild(geom);
        rootNode.attachChild(userNode);
        BillboardControl bbControl = new BillboardControl();
        bbControl.setAlignment(BillboardControl.Alignment.Screen);
        BitmapText thinking = new BitmapText(guiFont);
        thinking.setBox(new Rectangle(0, 0, 12, 6));
        thinking.setQueueBucket(Bucket.Translucent);
        thinking.setVerticalAlignment(VAlign.Center);
        thinking.setSize(1f);
        thinking.setLocalTranslation(0, 5, 0);
        thinking.setText("initializing...");
        thinking.addControl(bbControl);
        userNode.attachChild(thinking);
        userNode.setUserData("text", thinking);
        return userNode;
    }

    private Vector3f getCursorContactPoint() {
        Vector2f center = new Vector2f(cam.getWidth() / 2, cam.getHeight() / 2);
        Vector3f origin = cam.getWorldCoordinates(center, 0.0f);
        Vector3f direction = cam.getWorldCoordinates(center, 0.3f);
        direction.subtractLocal(origin).normalizeLocal();
        Ray mouseRay = new Ray(origin, direction);
        CollisionResults results = new CollisionResults();
        if (rootNode != null) {
            rootNode.collideWith(mouseRay, results);
        }
        CollisionResult closestCollision = results.getClosestCollision();
        if (closestCollision != null) {
            return closestCollision.getContactPoint();
        }
        return null;
    }
    private ActionListener actionListener = new ActionListener() {

        public void onAction(String name, boolean keyPressed, float tpf) {
            if (keyPressed) {
                System.out.println("keypressed : " + keyPressed);
                if (name.equals("Click")) {
                } else if (name.equals("key1")) {
                    Vector3f vect = getCursorContactPoint();
                    initAgent(1, vect);
                } else if (name.equals("key2")) {
                    Vector3f vect = getCursorContactPoint();
                    initAgent(2, vect);
                } else if (name.equals("key3")) {
                    Vector3f vect = getCursorContactPoint();
                    initAgent(3, vect);
                } else if (name.equals("key4")) {
                    Vector3f vect = getCursorContactPoint();
                    initAgent(4, vect);
                } else if (name.equals("key_L")) {
                    learnAttacks();
                }
            }
        }
    };

    public void initKeys() {
        inputManager.addMapping("key1", new KeyTrigger(KeyInput.KEY_1));
        inputManager.addMapping("key2", new KeyTrigger(KeyInput.KEY_2));
        inputManager.addMapping("key3", new KeyTrigger(KeyInput.KEY_3));
        inputManager.addMapping("key4", new KeyTrigger(KeyInput.KEY_4));
        inputManager.addMapping("key_L", new KeyTrigger(KeyInput.KEY_L));
        inputManager.addMapping("Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener(actionListener, new String[]{"Click", "key1", "key2", "key3", "key4", "key_L"});
    }

    private void updateYPosition(Spatial spat) {
        Vector3f vectPlayer = spat.getLocalTranslation();
        vectPlayer.y = terrain.getHeight(new Vector2f(vectPlayer.x, vectPlayer.z));
        spat.setLocalTranslation(vectPlayer);
    }

    public void initAgent(int type, Vector3f vect) {
        ColorRGBA color = ColorRGBA.Red;
        switch (type) {
            case 1:
                color = ColorRGBA.Red;
                break;
            case 2:
                color = ColorRGBA.Blue;
                break;
            case 3:
                color = ColorRGBA.Brown;
                break;
            case 4:
                color = ColorRGBA.Green;
                break;
            default:
                color = ColorRGBA.Red;
                break;
        }
        Node npc = addUser(color, new Vector2f(vect.x, vect.z));
        Test4Agent agent = new Test4Agent(0, 1000, (BitmapText) npc.getUserData("text"));
        agent.agentType = type;
        agent.setSpatial(npc);
        ai.addAgent(agent);
    }

    @Override
    public void simpleInitApp() {
        setDisplayFps(false);       // to hide the FPS
        setDisplayStatView(false);  // to hide the statistics 
        //stateManager.attach(new VideoRecorderAppState(1f, 30)); //start recording
        globalText = new BitmapText(guiFont, false);
        globalText.setSize(guiFont.getPreferredSize() * 1);
        globalText.setColor(ColorRGBA.Brown);
        globalText.setText("Global Text!");
        globalText.setLocalTranslation(0, cam.getHeight(), 0);
        guiNode.attachChild(globalText);
        cam.setLocation(new Vector3f(0, 60, 0));
        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
        addTerrain();
        env = new EnvironmentDefinition();
        env.setFactorDefinition("agentType", new FactorInteger(0, 0, 0, 0)); // just to split into situations each for type of agent.
        env.setFactorDefinition("closeEnemyType", new FactorInteger(0, 10, 0, 0)); // just to split into situations each for type of enemy.
        env.setFactorDefinition("isBored", new FactorBoolean(false, false, 1));
        env.setFactorDefinition("isRested", new FactorBoolean(false, true, 1));
        env.setFactorDefinition("inAttackRange", new FactorBoolean(false, true, 0));
        ai = new MotiveAI<Test4Agent>(env, LearningType.STANDARD, 100);
        ai.registerBehaviour(Test4AttackEnemy.class);
        ai.registerBehaviour(Test4CriticalAttackEnemy.class);
        ai.registerBehaviour(Test4PowerAttackEnemy.class);
        ai.registerBehaviour(Test4Idle.class);
        ai.registerBehaviour(Test4BoredMoving.class);
        ai.registerBehaviour(Test4MoveToEnemy.class);
        ai.setUpdateRate(0.2f);
        initKeys();
        ai.addSensor(new Test4Sensor());
        stateManager.attach(ai);
    }

    private void learnAttacks() {
        for (int number = 0; number <= 10; number++) {
            for (int i = 1; i <= 4; i++) {
                for (int j = 1; j <= 4; j++) {
                    initAgent(i, new Vector3f(0, 0, 0));
                    initAgent(j, new Vector3f(0, 0, 0));
                    ai.executeSimulation(200);
                    ai.removeAllAgents();
                }
            }
        }
    }

    @Override
    public void simpleUpdate(float tpf) {
        for (Iterator<Test4Agent> it = ai.getAgents().iterator(); it.hasNext();) {
            Test4Agent test4Agent = it.next();
            updateYPosition(test4Agent.getSpatial());
        }
        String globalInfomration = "";
        globalInfomration += "key 1-4 - add agent type 1-4 in center of view. press \"L\" to learn AI \n";
        globalInfomration += "attack(12dmg/100% chance) \n";
        globalInfomration += "Power attack(15dmg/100% chance) \n";
        globalInfomration += "Critical attack(40dmg/50% chance) \n";
        globalInfomration += "agent 1 is weak for \"Attack\"(30 dmg) \n";
        globalInfomration += "agent 2 and 3 have 66,(6)% chance to dodge \"Power attack\" \n";
        globalInfomration += "agent 4 have better \"Power Attack\"(30 dmg) \n";
        globalInfomration += "agent 3 have 90% chance to dodge \"Critical Attack\" \n";
        globalInfomration += "learning count left global: " + ai.learnCountLeft() + " \n";
        globalText.setText(globalInfomration);
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}
